package com.voiceqsologger.websocket;

import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.voiceqsologger.entity.QsoLog;
import com.voiceqsologger.service.QsoLogService;
import com.voiceqsologger.service.LlmQsoExtractorClient;
import com.voiceqsologger.service.CommEntityExtractorParser;
import com.voiceqsologger.config.OperatorProperties;
import com.voiceqsologger.config.AsrSegmentationProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 将实时识别文本解析为 QSO 项，并落库，按会话聚合；遇到结束语（如 73/七三/再见）时提交一条完整记录。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class QsoRealtimeOrchestrator {

    private final CommEntityExtractorParser parser;
    private final LlmQsoExtractorClient llmExtractor;
    private final QsoLogService qsoLogService;
    private final OperatorProperties operatorProperties;
    private final AsrSegmentationProperties segProperties;

    private static class SessionAgg {
        QsoLog current = new QsoLog();
        String qsoId;
        long lastFinalTs;
        boolean pendingNew;
        long pendingSince;
        long lastLlmCostMs;
    }

    private final Map<String, SessionAgg> sessions = new ConcurrentHashMap<>();

    public QsoLog onFinalText(String sessionId, String text) {
        SessionAgg agg = sessions.computeIfAbsent(sessionId, k -> {
            SessionAgg s = new SessionAgg();
            s.current.setCreatedAt(LocalDateTime.now());
            s.lastFinalTs = System.currentTimeMillis();
            return s;
        });

        // 增量合并解析到当前聚合对象（仅填充空字段，避免覆盖更准确的值）
        // 先用规则解析作为兜底
        String myCs = operatorProperties.getMyCallsign();
        QsoLog parsed = parser.parse(text, myCs);
        // 尝试用大模型解析，成功则优先使用模型结果进行补全
        long llmStart = System.currentTimeMillis();
        QsoLog ai = llmExtractor.extract(text);
        agg.lastLlmCostMs = System.currentTimeMillis() - llmStart;
        if (ai != null) {
            mergePreferExisting(parsed, ai);
        }
        // 待切分确认逻辑：首次命中条件仅标记 pending，新一段再确认
        if (!agg.pendingNew && shouldStartNewQso(agg, parsed, text)) {
            agg.pendingNew = true;
            agg.pendingSince = System.currentTimeMillis();
        } else if (agg.pendingNew) {
            boolean confirm = shouldStartNewQso(agg, parsed, text) || isQsoEnd(text) || (System.currentTimeMillis() - agg.pendingSince > segProperties.getConfirmTimeoutMs());
            if (confirm) {
                log.info("confirm start new QSO, oldId={}", agg.qsoId);
                sessions.remove(sessionId);
                SessionAgg s2 = new SessionAgg();
                s2.current.setCreatedAt(LocalDateTime.now());
                s2.lastFinalTs = System.currentTimeMillis();
                sessions.put(sessionId, s2);
                agg = s2;
            }
            agg.pendingNew = false;
        }

        mergePreferExisting(agg.current, parsed);

        // 首次插入，其后更新
        if (agg.qsoId == null) {
            Db.save(agg.current);
            agg.qsoId = agg.current.getId();
        } else {
            QsoLog update = new QsoLog();
            update.setId(agg.qsoId);
            update.setCallsign(agg.current.getCallsign());
            update.setSignalReport(agg.current.getSignalReport());
            update.setRig(agg.current.getRig());
            update.setAntenna(agg.current.getAntenna());
            update.setPower(agg.current.getPower());
            update.setHeight(agg.current.getHeight());
            update.setLocation(agg.current.getLocation());
            update.setRawText(agg.current.getRawText());
            Db.updateById(update);
        }

        // 检测结束语：数字 73、中文“七三”、常见致意“再见/谢谢/88”等
        if (isQsoEnd(text)) {
            try {
                log.info("QSO end detected, finalized logId: {}", agg.qsoId);
            } finally {
                sessions.remove(sessionId);
            }
        }
        agg.lastFinalTs = System.currentTimeMillis();
        return agg.current;
    }

    /** 获取并返回最近一次 LLM 解析耗时（毫秒），读取后不清零。 */
    public long getLastLlmCostMs(String sessionId) {
        SessionAgg s = sessions.get(sessionId);
        return s == null ? 0L : s.lastLlmCostMs;
    }

    /**
     * 手动强制开始新通联。
     */
    public void forceStartNew(String sessionId) {
        SessionAgg old = sessions.remove(sessionId);
        log.info("force start new QSO, oldId={}", old == null ? null : old.qsoId);
        SessionAgg s2 = new SessionAgg();
        s2.current.setCreatedAt(LocalDateTime.now());
        s2.lastFinalTs = System.currentTimeMillis();
        sessions.put(sessionId, s2);
    }

    private boolean shouldStartNewQso(SessionAgg agg, QsoLog inc, String text) {
        // 长静默分割：30 秒
        final long GAP_MS = segProperties.getGapMs();
        long now = System.currentTimeMillis();
        if (agg.qsoId != null && now - agg.lastFinalTs > GAP_MS) {
            return true;
        }
        // 呼号切换 + 常见交接线索
        String cur = upperOrNull(agg.current.getCallsign());
        String next = upperOrNull(inc.getCallsign());
        if (cur != null && next != null && !cur.equals(next)) {
            String u = text == null ? "" : text.toUpperCase();
            if (u.contains(" RST") || u.contains("信号报告") || isQsoEnd(u) || u.contains("这里") || u.contains("呼号")) {
                return true;
            }
        }
        return false;
    }

    private static String upperOrNull(String s) {
        if (s == null || s.trim().isEmpty()) return null;
        return s.toUpperCase();
    }

    private void mergePreferExisting(QsoLog base, QsoLog inc) {
        base.setRawText(join(base.getRawText(), inc.getRawText()));
        if (isBlank(base.getCallsign()) && notBlank(inc.getCallsign())) base.setCallsign(inc.getCallsign());
        if (isBlank(base.getSignalReport()) && notBlank(inc.getSignalReport())) base.setSignalReport(inc.getSignalReport());
        if (isBlank(base.getRig()) && notBlank(inc.getRig())) base.setRig(inc.getRig());
        if (isBlank(base.getAntenna()) && notBlank(inc.getAntenna())) base.setAntenna(inc.getAntenna());
        if (isBlank(base.getLocation()) && notBlank(inc.getLocation())) base.setLocation(inc.getLocation());
    }

    private boolean isQsoEnd(String text) {
        if (text == null) return false;
        String t = text.trim();
        String u = t.toUpperCase();
        return u.contains(" 73") || u.contains("73 ") || u.equals("73")
                || t.contains("七三") || t.contains("再见") || t.contains("谢谢") || t.contains("88");
    }

    private static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); }
    private static boolean notBlank(String s) { return !isBlank(s); }
    private static String join(String a, String b) {
        if (isBlank(a)) return b;
        if (isBlank(b)) return a;
        if (a.endsWith("\n")) return a + b;
        return a + "\n" + b;
    }
}


